package com.dandelion.business.service.impl;


import cn.hutool.json.JSONUtil;
import com.dandelion.business.entity.SysUser;
import com.dandelion.business.entity.vo.LoginUserDetail;
import com.dandelion.business.entity.vo.login.req.LoginReqVO;
import com.dandelion.business.entity.vo.login.resp.LoginUserInfo;
import com.dandelion.business.service.UserLoginService;
import com.dandelion.framework.common.R;
import com.dandelion.framework.common.RedisKey;
import com.dandelion.framework.common.util.JwtUtil;
import com.dandelion.framework.enums.CodeEnum;
import com.dandelion.framework.exceptions.ServerException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserLoginServiceImpl implements UserLoginService {


    /**
     * 访问令牌有效性
     */
    @Value("${dandelion.token.access-token-validity:3600000}")
    private Long accessTokenValidity;

    /**
     * 刷新令牌有效性,默认12小时
     */
    @Value("${dandelion.token.refresh-token-validity:43200000}")
    private Long refreshTokenValidity;


    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private AuthenticationManager authenticationManager;


    /**
     * 登录
     *
     * @param req 要求事情
     * @return {@link R}
     */
    @Override
    public R login(LoginReqVO req) {
        //使用AuthenticationManager的authenticate()进行用户认证
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(req.getUsername(), req.getPassword());
        //认证之后的对象
        Authentication authenticate = null;
        try {
            //在这里会去调用com.dandelion.business.service.impl.UserDetailServiceImpl.loadUserByUsername方法做用户认证
            authenticate = authenticationManager.authenticate(authentication);
        } catch (AuthenticationException e) {
            //拦截异常,如果有异常说明认证出现问题
            if (e instanceof LockedException) {
                log.warn("账户:{}被锁定", req.getUsername());
                throw new ServerException(CodeEnum.USER_IS_LOCK.getCode(), CodeEnum.USER_IS_LOCK.getMsg());
            }
            if (e instanceof DisabledException) {
                log.warn("账户:{}被禁用", req.getUsername());
                throw new ServerException(CodeEnum.USER_IS_DISABLE.getCode(), CodeEnum.USER_IS_DISABLE.getMsg());
            }
            if (e instanceof BadCredentialsException) {
                log.warn("账户:{}凭证异常", req.getUsername());
                throw new ServerException(CodeEnum.UNAUTHORIZED.getCode(), CodeEnum.UNAUTHORIZED.getMsg());
            }
            log.warn("账户:{}为匹配到应有异常,抛出错误异常", req.getUsername());
            throw new ServerException(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMsg());
        }
        //如果认证没有通过给出对应的提示
        if (Objects.isNull(authenticate)) {
            log.warn("用户:{}认证出现问题,authenticate为空", req.getUsername());
            throw new ServerException(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMsg());
        }
        //认证通过,使用userid生成一个jwt,存入redis
        LoginUserDetail loginUserDetail = (LoginUserDetail) authenticate.getPrincipal();
        SysUser user = loginUserDetail.getUser();

        String userId = loginUserDetail.getUser().getId().toString();

        LoginUserInfo baseInfo = new LoginUserInfo();
        baseInfo.setUserId(user.getId());
        baseInfo.setUserName(user.getUserName());
        baseInfo.setStatus(user.getState());
        baseInfo.setAvatar(user.getAvatar());

        //生成token
        String accessToken = JwtUtil.createJWT(JSONUtil.toJsonStr(baseInfo), accessTokenValidity);

        //生成刷新token
        String refreshToken = JwtUtil.createJWT(JSONUtil.toJsonStr(baseInfo), refreshTokenValidity);

        String userRefreshTokenKey = RedisKey.LoginModule.USER_REFRESH_TOKEN_PREFIX + userId;
        //refreshTokenValidity + 300000  为了起到一个兼容的作用
        redisTemplate.opsForValue().set(userRefreshTokenKey, refreshToken, refreshTokenValidity + 300000, TimeUnit.MILLISECONDS);

        //redis的key
        String loginUserInfoKey = RedisKey.LoginModule.LOGIN_USER_INFO_PREFIX + userId;
        //refreshTokenValidity + 300000  为了起到一个兼容的作用
        redisTemplate.opsForValue().set(loginUserInfoKey, JSONUtil.toJsonStr(loginUserDetail), refreshTokenValidity + 300000, TimeUnit.MILLISECONDS);

        //返回R
        return R.ok().data(baseInfo).put("accessToken", accessToken).put("refreshToken", refreshToken);

    }

    /**
     * 注销
     *
     * @return {@link R}
     */
    @Override
    public R logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUserDetail loginUserDetail = (LoginUserDetail) authentication.getPrincipal();
        if (loginUserDetail != null) {
            String loginUserInfoKey = RedisKey.LoginModule.LOGIN_USER_INFO_PREFIX + loginUserDetail.getUser().getId();
            redisTemplate.delete(loginUserInfoKey);
        }
        return R.ok();
    }
}
